package com.flsldl.mydb.backend.common;

import com.flsldl.mydb.common.Error;

import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName AbstractCache
 * @Description TODO
 * @Author @O_o @Date 2023/2/15 下午 8:40
 *
 * Data Manager
 *  DM 直接管理数据库 DB 文件和日志文件。
 *  上层模块和文件系统之间的一个抽象层，向下直接读写文件，向上提供数据的包装；另外就是日志功能。
 *  DM 的主要职责有：
 *      1) 分页管理 DB 文件，并进行缓存；
 *      2) 管理日志文件，保证在发生错误时可以根据日志进行恢复；
 *      3) 抽象 DB 文件为 DataItem 供上层模块使用，并提供缓存。
 */

// AbstractCache 实现了一个引用计数策略的缓存
// 其他缓存只需要继承这个类，实现 getForCache 和 releaseForCache 两个抽象方法即可
public abstract class AbstractCache<T> {

    // 实际缓存的数据
    private HashMap<Long, T> cache;
    // 元素的引用个数
    private HashMap<Long, Integer> references;
    // 正在获取某资源的线程
    private HashMap<Long, Boolean> getting;

    // 缓存的最大缓存资源数
    private int maxResource;
    // 缓存中元素的个数
    private int count = 0;
    private Lock lock;

    public AbstractCache(int maxResource) {
        this.maxResource = maxResource;
        cache = new HashMap<>();
        references = new HashMap<>();
        getting = new HashMap<>();
        lock = new ReentrantLock();
    }

    // ----------------------------------(0v0)--> 分割线--------------------------------------------------------------

    // 进入一个死循环，来无限尝试从缓存里获取
    protected T get(long key) throws Exception {
        while (true) {
            // 1. 判断请求的资源有没有被其他线程获取 ==> true 线程休眠
            lock.lock();
            if (getting.containsKey(key)) {
                // 请求的资源正在被其他线程获取
                lock.unlock();
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    continue;
                }
                continue;
            }
            // 2. 判断缓存中是否有请求的资源 ==> true 返回资源、方法结束
            if(cache.containsKey(key)) {
                // 资源在缓存中，直接返回
                T obj = cache.get(key);
                references.put(key, references.get(key) + 1);
                lock.unlock();
                return obj;
            }
            // 3. 判断缓存大小是否还有空间增加新的资源 ==> true 缓存已满，报错
            if (maxResource > 0 && count == maxResource) {
                lock.unlock();
                throw Error.CacheFullException;
            }
            // 4. 增加已缓存的资源个数，正在获取该资源
            count ++;
            getting.put(key,true);
            lock.unlock();
            break;
        }
        // 5. 尝试从数据源获取资源
        T obj = null;
        try {
            obj = getForCache(key);
        } catch (Exception e) {
            // 失败，回滚
            lock.lock();
            count --;
            getting.remove(key);
            lock.unlock();
            throw e;
        }
        // 成功，资源获取结束，资源放入缓存中
        lock.lock();
        getting.remove(key);
        cache.put(key,obj);
        references.put(key,1);
        lock.unlock();

        return obj;

    }

    // ----------------------------------(0v0)--> 分割线--------------------------------------------------------------

    // 强行释放一个缓存
    protected void release(long key) {
        lock.lock();
        try {
            // 资源引用次数 -1，若为 0 ==> 回源，删除缓存中所有有关的结构
            int ref = references.get(key) - 1;
            if (ref == 0) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
                count --;
            } else {
                references.put(key, ref);
            }
        } finally {
            lock.unlock();
        }
    }

    // 关闭缓存，写回所有资源
    protected void close() {
        lock.lock();
        try {
            Set<Long> keys = cache.keySet();
            for (long key : keys) {
                T obj = cache.get(key);
                releaseForCache(obj);
                references.remove(key);
                cache.remove(key);
            }
        } finally {
            lock.unlock();
        }
    }

    // ----------------------------------(0v0)--> 分割线--------------------------------------------------------------

    // 当资源不在缓存时的获取行为
    protected abstract T getForCache(long key) throws Exception;
    // 当资源被驱逐时的写回行为
    protected abstract void releaseForCache(T obj);

}
